/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.pedidos.servidor.mb;

import br.com.pedidos.servidor.entity.Categoria;
import br.com.pedidos.servidor.entity.Mesa;
import br.com.pedidos.servidor.mb.model.ProdutoDataModel;
import br.com.pedidos.servidor.entity.Pedido;
import br.com.pedidos.servidor.entity.PedidoProdutoQuantidade;
import br.com.pedidos.servidor.entity.Produto;
import br.com.pedidos.servidor.exception.CadastraPedidoComContaFechadaException;
import br.com.pedidos.servidor.exception.IdMesaException;
import br.com.pedidos.servidor.exception.QuantidadeDeProdutosVazioException;
import br.com.pedidos.servidor.mb.observer.MesaObservador;
import br.com.pedidos.servidor.mb.observer.PedidoObservado;
import br.com.pedidos.servidor.rn.CategoriaRN;
import br.com.pedidos.servidor.rn.MesaRN;
import br.com.pedidos.servidor.rn.PedidoProdutoQuantidadeRN;
import br.com.pedidos.servidor.rn.PedidoRN;
import br.com.pedidos.servidor.rn.ProdutoRN;
import br.com.pedidos.servidor.util.Constantes;
import br.com.pedidos.servidor.util.Monetario;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import org.primefaces.push.EventBus;
import org.primefaces.push.EventBusFactory;

/**
 *
 * @author Weslley
 */
@ManagedBean
@SessionScoped
public class PedidoMB implements MesaObservador, Serializable {

    private static final long serialVersionUID = 1L;

    private PedidoRN pedidoRN;
    private PedidoProdutoQuantidadeRN ppkRN;
    private ProdutoRN produtoRN;
    private MesaRN mesaRN;
    private CategoriaRN categoriaRN;

    private Pedido pedidoASalvar;
    private ProdutoDataModel listaProdutosPorCategoria;
    private List<Produto> produtosSelecionados;
    private List<Produto> produtos = new ArrayList<>();

    //Listas de armazenamento das categorias e dos produtos.
    private List<Categoria> listaCategorias;

    //Lista todos os produtos de um dado pedido.
    private List<Produto> listaProdutosPedidos;
    private Produto produtoSelecionado;

    private int idMesa;

    private Pedido pedidoAux;

    private String parametro;
    private PedidoObservado obs;
    private Integer quantidade;

    //Variável que simula um ponteiro pra indicar a posição na lista de categorias.
    private int posicaoCategoria = -1;

    //Objeto auxiliar que será usado apenas para referenciar a categoria atual na view do usuário.
    private Categoria categoriaAtual;

    private List<Pedido> pedidosHabilitaBotao = new ArrayList<>();
    private boolean contaEncerrada = false;
    private boolean liberaFazerPedido = true;

    public PedidoMB() {
        categoriaRN = new CategoriaRN();
        mesaRN = new MesaRN();
        produtoRN = new ProdutoRN();
        pedidoRN = new PedidoRN();
        ppkRN = new PedidoProdutoQuantidadeRN();
        produtosSelecionados = new ArrayList<>();
        pedidoASalvar = new Pedido();
        produtoSelecionado = new Produto();
        listarCategoria();
        if (posicaoCategoria == -1) {
            listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(listaCategorias.get(0)));
            posicaoCategoria = 0;
            categoriaAtual = listaCategorias.get(0);
        }
    }

    //------------------------------------- MÉTODOS UTILIZADOS PARA INTERAÇÃO COM O USUÁRIO CELULAR.
    /**
     * Método que persiste e informa pro controlador que o pedido foi
     * registrado.
     */
    public void cadastraPedidoQuantidadeProdutos() {
        try {
            if (this.idMesa <= 0 || this.idMesa > 40) {
                throw new IdMesaException();
            }

            if (RepositorioControlador.getControladorPedidoInstance() == null) {
                throw new InstantiationException();
            }

            if (produtosSelecionados.isEmpty()) {
                throw new ArrayStoreException();
            }

            if (produtosSelecionados.isEmpty() == false) {
                boolean tudoVazio = true;
                for (Produto p : produtosSelecionados) {
                    if (p.getQuantidadeVenda() > 0) {
                        tudoVazio = false;
                        break;
                    }
                }
                if (tudoVazio == true) {
                    throw new QuantidadeDeProdutosVazioException();
                }
            }

            obs = RepositorioControlador.getControladorPedidoInstance();

            Mesa mesa = mesaRN.mesaPorNumero(this.idMesa);
            if (mesa.getStatus().equalsIgnoreCase(Constantes.MESA_LIVRE)) {
                mesa.setStatus(Constantes.MESA_EM_SERVICO);
            }

            pedidoASalvar.setStatus(Constantes.PENDENTE);
            pedidoASalvar.setTipo(Constantes.PEDIDO_PRODUTO);
            pedidoASalvar.setMesa(mesa);
            pedidoRN.salvaPedido(pedidoASalvar);

            Float total = (float) 0;

            for (Produto produto : produtosSelecionados) {

                if (produto.getQuantidadeVenda() == 0 || produto.getQuantidadeVenda() == null) {
                    throw new IllegalStateException();
                }

                PedidoProdutoQuantidade pedProd = new PedidoProdutoQuantidade();

                pedProd.setPedido(pedidoASalvar);
                pedProd.setProduto(produto);
                pedProd.setQuantidade(produto.getQuantidadeVenda());

                //Incrementa o valor do pedido.
                total = total + (produto.getPreco() * produto.getQuantidadeVenda());

                ppkRN.salvaPPK(pedProd);

                produto.setQuantidadeVenda(0);
            }

            pedidoASalvar.setTotal(total);
            pedidoRN.atualizaPedido(pedidoASalvar);

            obs.registraMesaObservador(idMesa, this);
            obs.registraPedido(pedidoASalvar);

            pedidoASalvar = new Pedido();
            produtosSelecionados = new ArrayList<>();

            FacesContext.getCurrentInstance().addMessage("msgMenor", new FacesMessage("Pedido anotado!", null));

        } catch (InstantiationException e) {
            produtosSelecionados = new ArrayList<>();
            pedidoASalvar = new Pedido();
            quantidade = 0;
            FacesContext.getCurrentInstance().addMessage("default", new FacesMessage("O atendimento ainda não começou. Aguarde...", null));
        } catch (IdMesaException e) {
            produtosSelecionados = new ArrayList<>();
            pedidoASalvar = new Pedido();
            quantidade = 0;
            FacesContext.getCurrentInstance().addMessage("msgMaior", new FacesMessage("Identificamos uma falha no pedido. Aponte novamente no QR code da mesa!", null));
        } catch (ArrayStoreException e) {
            produtosSelecionados = new ArrayList<>();
            pedidoASalvar = new Pedido();
            quantidade = 0;
            FacesContext.getCurrentInstance().addMessage("msgMaior", new FacesMessage("Ops! É necessário selecionar pelo menos um item para fazer o pedido.", null));
        } catch (IllegalStateException e) {
            produtosSelecionados = new ArrayList<>();
            pedidoASalvar = new Pedido();
            quantidade = 0;
            FacesContext.getCurrentInstance().addMessage("default", new FacesMessage("Defina a quantidade antes de fazer o pedido.", null));
        } catch (QuantidadeDeProdutosVazioException e) {
            produtosSelecionados = new ArrayList<>();
            pedidoASalvar = new Pedido();
            quantidade = 0;
            FacesContext.getCurrentInstance().addMessage("msgMaior", new FacesMessage("Ops! É necessário definir a quantidade do produto escolhido para fazer o pedido.", null));
        }
    }

    /**
     * Responsável por encerrar a conta de uma mesa.
     *
     */
    public void fechaConta() {
        try {

            if (this.idMesa <= 0 || this.idMesa > 40) {
                throw new IdMesaException();
            }

            if (RepositorioControlador.getControladorPedidoInstance() == null) {
                throw new InstantiationException();
            }
            Mesa mesa = mesaRN.mesaPorNumero(idMesa);

            Pedido p = new Pedido();
            p.setTipo(Constantes.PEDIDO_CONTA);
            p.setStatus(Constantes.AGUARDO_CONTA);
            p.setMesa(mesa);

            pedidoRN.salvaPedido(p);

            obs = RepositorioControlador.getControladorPedidoInstance();
            obs.registraMesaObservador(idMesa, this);
            obs.registraEncerraConta(p);

            setContaEncerrada(true);

            FacesContext.getCurrentInstance().addMessage("default", new FacesMessage("Pronto! Em instantes sua conta será entregue.", null));

        } catch (IdMesaException e) {
            FacesContext.getCurrentInstance().addMessage("msgMaior", new FacesMessage("Identificamos uma falha no pedido de conta. Aponte novamente no QR code da mesa!", null));
        } catch (InstantiationException e) {
            FacesContext.getCurrentInstance().addMessage("default", new FacesMessage("O atendimento ainda não começou. Aguarde...", null));
        }
    }

    /**
     * Remove o produto selecionado do carrinho.
     *
     * @param p
     */
    public void removeProdutoCarrinho(Produto p) {
        produtosSelecionados.remove(p);
    }

    /**
     * Método responsável pela adição de um produto na lista de produtos do
     * pedido atual.
     *
     */
    public void adicionaProdutoNaListaProdSelec() {

        try {
            if (RepositorioControlador.getControladorPedidoInstance() == null) {
                throw new InstantiationException();
            }
            if (liberaFazerPedido == false) {
                liberaFazerPedido = pedidoRN.verificaSeExistePedidoContaAbertoPorMesa(idMesa);
                if (liberaFazerPedido == false) {
                    throw new CadastraPedidoComContaFechadaException();
                }
            }
            if (produtoSelecionado.getQuantidadeVenda() == 0 || produtoSelecionado.getQuantidadeVenda() == null) {
            } else {
                if ((produtosSelecionados.isEmpty() == false)) {
                    boolean existe = false;
                    for (Produto prod : produtosSelecionados) {
                        if (Objects.equals(prod.getId(), produtoSelecionado.getId())
                                && produtosSelecionados.contains(produtoSelecionado)) {
                            prod.incrementaQtdVenda(produtoSelecionado.getQuantidadeVenda());
                            listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(categoriaAtual));
                            produtoSelecionado = new Produto();
                            existe = true;
                            FacesContext.getCurrentInstance().addMessage("msgMenor", new FacesMessage("Produto adicionado ao carrinho.", null));
                            break;
                        } else if (Objects.equals(prod.getId(), produtoSelecionado.getId())
                                && !(produtosSelecionados.contains(produtoSelecionado))) {
                            prod.incrementaQtdVenda(prod.getQuantidadeVenda() + produtoSelecionado.getQuantidadeVenda());
                            listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(categoriaAtual));
                            produtoSelecionado = new Produto();
                            existe = true;
                            FacesContext.getCurrentInstance().addMessage("msgMenor", new FacesMessage("Produto adicionado ao carrinho.", null));
                            break;
                        }
                    }
                    if (existe == false) {
                        produtosSelecionados.add(produtoSelecionado);
                        listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(categoriaAtual));
                        produtoSelecionado = new Produto();
                        FacesContext.getCurrentInstance().addMessage("msgMenor", new FacesMessage("Produto adicionado ao carrinho.", null));
                    }
                } else {
                    produtosSelecionados.add(produtoSelecionado);
                    listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(categoriaAtual));
                    produtoSelecionado = new Produto();
                    FacesContext.getCurrentInstance().addMessage("msgMenor", new FacesMessage("Produto adicionado ao carrinho.", null));
                }
            }
        } catch (InstantiationException e) {
            produtosSelecionados = new ArrayList<>();
            pedidoASalvar = new Pedido();
            quantidade = 0;
            FacesContext.getCurrentInstance().addMessage("default", new FacesMessage("O atendimento ainda não começou. Aguarde...", null));
        } catch (CadastraPedidoComContaFechadaException c) {
            produtosSelecionados = new ArrayList<>();
            pedidoASalvar = new Pedido();
            quantidade = 0;
            FacesContext.getCurrentInstance().addMessage("msgMaior", new FacesMessage("Não foi possível anotar o seu pedido! Já existe uma solicitação de conta em aberto.", null));
        }

    }

    /**
     * Realiza o cancelamento de um determinado pedido desde que o seu
     * estadopedidoAuxainda esteja como 'pendente'.
     *
     */
    public void cancelaPedidoFeito() {
        try {
            if (pedidoRN.pedidoPorId(pedidoAux.getId()).getStatus().equalsIgnoreCase(Constantes.PENDENTE)) {

                if (RepositorioControlador.getControladorPedidoInstance() == null) {
                    throw new InstantiationException();
                }
                pedidoAux.setStatus(Constantes.CANCELADO);
                pedidoRN.atualizaPedido(pedidoAux);

                obs = RepositorioControlador.getControladorPedidoInstance();

                obs.cancelaPedido(pedidoAux);

                if (verificaStatusTodosPedidos()) {
                    encerraECancelaPedidosMesaAberta();
                }

                FacesContext.getCurrentInstance().addMessage("msgMenor", new FacesMessage("Pronto! Pedido cancelado"));
            } else {
                FacesContext.getCurrentInstance().addMessage("default", new FacesMessage("Desculpe, o seu pedido já está em preparo."));
            }
        } catch (InstantiationException e) {
            FacesContext.getCurrentInstance().addMessage("default", new FacesMessage("O atendimento ainda não começou. Aguarde...", null));
        }
    }

    //------------------------------------- MÉTODOS DE VALIDAÇÃO E/OU AÇÃO DA VIEW DO USUÁRIO CELULAR.
    /**
     * Método utilizado para realizar mudança de categoria. Esse método vai
     * receber um chamado de uma action e vai, dependendo do valor recebido,
     * incrementar ou decrementar um contador responsável por apontar qual a
     * categoria atual que será exibida.
     *
     * @param i
     */
    public void mudaCategoria(int i) {
        posicaoCategoria += i;
        if (posicaoCategoria < 0) {
            posicaoCategoria = (listaCategorias.size() - 1);
            categoriaAtual = listaCategorias.get(posicaoCategoria);
            listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(categoriaAtual));
        } else if (posicaoCategoria >= listaCategorias.size()) {
            posicaoCategoria = 0;
            categoriaAtual = listaCategorias.get(posicaoCategoria);
            listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(categoriaAtual));
        } else {
            categoriaAtual = listaCategorias.get(posicaoCategoria);
            listaProdutosPorCategoria = new ProdutoDataModel(listarProduto(categoriaAtual));
        }

    }

    /**
     * Método responsável por habilitar e/ou desabilitar o botão de pedir a
     * conta na tela de histórico.
     *
     * @return boolean
     */
    public boolean disableBotaoConta() {
        if (contaEncerrada == false) {
            for (Pedido pedido : pedidosHabilitaBotao) {
                if (pedido.getStatus().equalsIgnoreCase(Constantes.PENDENTE) || pedido.getStatus().equalsIgnoreCase(Constantes.AGUARDO_CONTA)) {
                    if (pedido.getStatus().equalsIgnoreCase(Constantes.AGUARDO_CONTA)) {
                        liberaFazerPedido = false;
                    }
                    return true;
                }
            }
        } else if (contaEncerrada == true) {
            setContaEncerrada(false);
            return true;
        }
        return false;
    }

    /**
     * Recebe um produto e seta o mesmo na variável produtoSelecionado. Método
     * utilizado para armazenar um produto oriundo de uma lista e que será
     * utilizado na tela de adição do produto na lista de produtos de um pedido.
     *
     * @param p
     */
    public void exibeDetalheProdutoASerAdicionado(Produto p) {
        produtoSelecionado = new Produto();
        produtoSelecionado = p;
    }

    public boolean verificaStatusPedidoEmDetalhe() {
        boolean exibeBotao = false;
        if (pedidoAux != null) {
            exibeBotao = pedidoRN.verificaSeStatusPedidoEhPendente(pedidoAux.getId());
        }
        return exibeBotao;
    }

    /**
     * Método responsável pela atualização da quantidade de pedidos no ícone do
     * carrinho.
     *
     * @return String
     */
    public String mostrarQtdPedidoCarrinho() {
        if (produtosSelecionados.size() > 0) {
            return "display: inline;";
        } else {
            return "display: none;";
        }
    }

    /**
     * Método que retorna um valor no formato R$.
     *
     * @return String
     */
    public String valorPedidoMonetario() {
        BigDecimal valorPedido = new BigDecimal(BigInteger.ZERO);
        for (Produto produto : produtosSelecionados) {
            BigDecimal valorProdutos = Monetario.multiplicar(new BigDecimal(produto.getQuantidadeVenda()), new BigDecimal(produto.getPreco()));
            valorPedido = Monetario.somar(valorPedido, valorProdutos);
        }
        return Monetario.toMoney(valorPedido);
    }

    /**
     * Método responsável pela exibição/não exibição da mensagem de carrinho
     * vazio na tela do carrino
     *
     * @return String
     */
    public String mostrarMensagemCarrinho() {
        if (produtosSelecionados.size() > 0) {
            return "display: none;";
        } else {
            return "display: inline;";
        }
    }

    //------------------------------------- MÉTODOS LISTAGENS NA VIEW DO USUÁRIO CELULAR.
    /**
     * Retorna uma lista que contém os produtos que foram selecionados para
     * aquele pedido.
     *
     * @return List<>
     */
    public ProdutoDataModel exibirProdutosSelecionadosCarrinho() {
        return new ProdutoDataModel(produtosSelecionados);
    }

    /**
     * Retorna uma lista com o histórico de pedidos da mesa.
     *
     * @return List<>
     */
    public List<Pedido> exibirHistoricoPedidos() {
        List<Pedido> lista = new ArrayList<>();

        Mesa mesa = mesaRN.mesaPorNumero(this.idMesa);

        if (mesa.getPedidosPorCliente() != null) {
            List<Long> idPedidos = processaStringEntrada(mesa);
            for (Long id : idPedidos) {
                Pedido pedido = pedidoRN.pedidoPorId(id);
                lista.add(pedido);
            }
        }
        return lista;
    }

    /**
     * Popula a lista de produtos de um determinado pedido.
     *
     * Recebe o pedido selecionado como parâmetro e cria uma lista de todos os
     * produtos com suas quantidades para serem exibidos na tela de detalhes do
     * pedido.
     *
     * @param p
     */
    public void exibeDetalhesPedido(Pedido p) {

        pedidoAux = new Pedido();
        pedidoAux = p;
        listaProdutosPedidos = new ArrayList<>();

        List<PedidoProdutoQuantidade> listaPedProdQtd = ppkRN.listaPPKPorPedido(p.getId());

        for (PedidoProdutoQuantidade ppk : listaPedProdQtd) {
            Produto prod = produtoRN.produtoPorId(ppk.getProduto().getId());
            prod.setQuantidadeVenda(ppk.getQuantidade());
            listaProdutosPedidos.add(prod);
        }
    }

    public List<Pedido> getListaPedidos(int numero) {
        pedidosHabilitaBotao = new ArrayList<>();
        List<Pedido> pedidos = pedidoRN.listaPedidosPorMesaAberta(numero);
        int cont = 1;
        for (Pedido pedido : pedidos) {
            pedido.setNumeroPedido(cont++);
        }

        pedidosHabilitaBotao = pedidos;
        return pedidos;
    }

    private List<Produto> listarProduto(Categoria cat) {
        List<Produto> listaProdutos = produtoRN.listaProdutosPorCategoria(cat.getId());
        return listaProdutos;
    }

    private void listarCategoria() {
        listaCategorias = new ArrayList<>();
        listaCategorias = categoriaRN.listaTodasCategorias();
    }

    //------------------------------------- MÉTODOS AUXILIARES INTERNOS
    private boolean verificaStatusTodosPedidos() {
        boolean todosCancelados = pedidoRN.verificaSeTodosPedidosCanceladosPorMesa(idMesa);
        return todosCancelados;
    }

    private void encerraECancelaPedidosMesaAberta() {
        List<Pedido> pedidos = pedidoRN.listaPedidosPorMesaAberta(idMesa);

        for (Pedido p : pedidos) {
            p.setStatus(Constantes.ENCERRADO_CANCELADO);
            pedidoRN.atualizaPedido(p);
        }
    }

    /**
     * Método que carrega o histórico de pedidos por mesa.
     *
     * @param mesa
     * @return List<Long>
     */
    private List<Long> processaStringEntrada(Mesa mesa) {

        List<Long> list = new ArrayList<>();

        // Tratar posições repetidas.
        String posicoes;

        posicoes = mesa.getPedidosPorCliente();

        /// passa a string e o delimitador pra quebrar os valores
        StringTokenizer tnzer = new StringTokenizer(posicoes, ",");

        String token;

        while (tnzer.hasMoreTokens()) {
            token = tnzer.nextToken();
            token = token.replaceAll(" ", "");
            Long posicao = Long.parseLong(token);
            Long idPedido;
            idPedido = posicao;
            list.add(idPedido);
        }

        return list;
    }

    //------------------------------------- MÉTODOS DO PUSH
    /**
     * Método responsável pela atualização da view de histórico pelo usuário
     * celular.
     */
    @Override
    public void atualizaHistorico() {
        atualizaTelaHistoricoMesa();
    }

    private void atualizaTelaHistoricoMesa() {
        EventBus eventBus = EventBusFactory.getDefault().eventBus();
        eventBus.publish("/" + Integer.toString(this.idMesa), new FacesMessage("atualizando histórico"));
    }

    @Override
    public void atualizaLiberaFazerPedido() {
        liberaFazerPedido = true;
    }

    // GETTERS AND SETTERS
    public Pedido getPedidoASalvar() {
        return pedidoASalvar;
    }

    public void setPedidoASalvar(Pedido pedidoASalvar) {
        this.pedidoASalvar = pedidoASalvar;
    }

    public List<Produto> getProdutosSelecionados() {
        return produtosSelecionados;
    }

    public void setProdutosSelecionados(List<Produto> produtosSelecionados) {
        this.produtosSelecionados = produtosSelecionados;
    }

    public Produto getProdutoSelecionado() {
        return produtoSelecionado;
    }

    public void setProdutoSelecionado(Produto produtoSelecionado) {
        this.produtoSelecionado = produtoSelecionado;
    }

    public Integer getQuantidade() {
        return quantidade;
    }

    public void setQuantidade(Integer quantidade) {
        this.quantidade = quantidade;
    }

    public int getIdMesa() {
        return idMesa;
    }

    public void setIdMesa(int idMesa) {
        this.idMesa = idMesa;
    }

    public String getParametro() {
        return parametro;
    }

    public void setParametro(String parametro) {
        this.parametro = parametro;
        parametro = parametro.split("X")[1];
        parametro = parametro.split("k")[0];
        setIdMesa(Integer.parseInt(parametro));
    }

    public List<Produto> getProdutos() {
        return produtos;
    }

    public void setProdutos(List<Produto> produtos) {
        this.produtos = produtos;
    }

    public Categoria getCategoriaAtual() {
        return categoriaAtual;
    }

    public void setCategoriaAtual(Categoria categoriaAtual) {
        this.categoriaAtual = categoriaAtual;
    }

    public ProdutoDataModel getListaProdutosPorCategoria() {
        return listaProdutosPorCategoria;
    }

    public void setListaProdutosPorCategoria(ProdutoDataModel listaProdutosPorCategoria) {
        this.listaProdutosPorCategoria = listaProdutosPorCategoria;
    }

    public List<Produto> getListaProdutosPedidos() {
        return listaProdutosPedidos;
    }

    public void setListaProdutosPedidos(List<Produto> listaProdutosPedidos) {
        this.listaProdutosPedidos = listaProdutosPedidos;
    }

    public boolean isContaEncerrada() {
        return contaEncerrada;
    }

    public void setContaEncerrada(boolean contaEncerrada) {
        this.contaEncerrada = contaEncerrada;
    }

}
